/*-------------------------------------------------------------------------
 *
 * to_tsany.c
 *		to_ts* function definitions
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/to_tsany.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "common/jsonapi.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#include "utils/jsonfuncs.h"


/*
 * 不透明数据结构，由parse_tsquery()传递给pushval_morph()。
 */
typedef struct MorphOpaque
{
	Oid			cfg_id;

	/*
	 * 单个tsquery形态可以被解析为多个单词。当这些单词位于相邻的位置时，它们使用这个运算符连接。通常，这就是OP_PHRASE，它要求复杂形态的单词位置完全匹配tsvector。
	 */
	int			qoperator;
} MorphOpaque;

typedef struct TSVectorBuildState
{
	ParsedText *prs;
	Oid			cfgId;
} TSVectorBuildState;

static void fc_add_to_tsvector(void *fc__state, char *fc_elem_value, int fc_elem_len);


Datum get_current_ts_config(PG_FUNCTION_ARGS)
{
	PG_RETURN_OID(getTSCurrentConfig(true));
}

/*
 * to_tsvector
 */
static int fc_compareWORD(const void *fc_a, const void *fc_b)
{
	int			fc_res;

	fc_res = tsCompareString(((const ParsedWord *) fc_a)->word, ((const ParsedWord *) fc_a)->len,
						  ((const ParsedWord *) fc_b)->word, ((const ParsedWord *) fc_b)->len,
						  false);

	if (fc_res == 0)
	{
		if (((const ParsedWord *) fc_a)->pos.pos == ((const ParsedWord *) fc_b)->pos.pos)
			return 0;

		fc_res = (((const ParsedWord *) fc_a)->pos.pos > ((const ParsedWord *) fc_b)->pos.pos) ? 1 : -1;
	}

	return fc_res;
}

static int fc_uniqueWORD(ParsedWord *fc_a, int32 fc_l)
{
	ParsedWord *fc_ptr,
			   *fc_res;
	int			fc_tmppos;

	if (fc_l == 1)
	{
		fc_tmppos = LIMITPOS(fc_a->pos.pos);
		fc_a->alen = 2;
		fc_a->pos.apos = (uint16 *) palloc(sizeof(uint16) * fc_a->alen);
		fc_a->pos.apos[0] = 1;
		fc_a->pos.apos[1] = fc_tmppos;
		return fc_l;
	}

	fc_res = fc_a;
	fc_ptr = fc_a + 1;

	/*
	 * 按单词及其位置排序
	 */
	qsort((void *) fc_a, fc_l, sizeof(ParsedWord), fc_compareWORD);

	/*
	 * 初始化第一个单词及其第一个位置
	 */
	fc_tmppos = LIMITPOS(fc_a->pos.pos);
	fc_a->alen = 2;
	fc_a->pos.apos = (uint16 *) palloc(sizeof(uint16) * fc_a->alen);
	fc_a->pos.apos[0] = 1;
	fc_a->pos.apos[1] = fc_tmppos;

	/*
	 * 汇总每个单词的位置信息
	 */
	while (fc_ptr - fc_a < fc_l)
	{
		if (!(fc_ptr->len == fc_res->len &&
			  strncmp(fc_ptr->word, fc_res->word, fc_res->len) == 0))
		{
			/*
			 * 得到一个新单词，所以把它放入结果中
			 */
			fc_res++;
			fc_res->len = fc_ptr->len;
			fc_res->word = fc_ptr->word;
			fc_tmppos = LIMITPOS(fc_ptr->pos.pos);
			fc_res->alen = 2;
			fc_res->pos.apos = (uint16 *) palloc(sizeof(uint16) * fc_res->alen);
			fc_res->pos.apos[0] = 1;
			fc_res->pos.apos[1] = fc_tmppos;
		}
		else
		{
			/*
			 * 该单词已存在，因此调整位置的信息。但是在此之前，我们应该检查位置数组的大小、位置的最大允许值和位置的唯一性
			 */
			pfree(fc_ptr->word);
			if (fc_res->pos.apos[0] < MAXNUMPOS - 1 && fc_res->pos.apos[fc_res->pos.apos[0]] != MAXENTRYPOS - 1 &&
				fc_res->pos.apos[fc_res->pos.apos[0]] != LIMITPOS(fc_ptr->pos.pos))
			{
				if (fc_res->pos.apos[0] + 1 >= fc_res->alen)
				{
					fc_res->alen *= 2;
					fc_res->pos.apos = (uint16 *) repalloc(fc_res->pos.apos, sizeof(uint16) * fc_res->alen);
				}
				if (fc_res->pos.apos[0] == 0 || fc_res->pos.apos[fc_res->pos.apos[0]] != LIMITPOS(fc_ptr->pos.pos))
				{
					fc_res->pos.apos[fc_res->pos.apos[0] + 1] = LIMITPOS(fc_ptr->pos.pos);
					fc_res->pos.apos[0]++;
				}
			}
		}
		fc_ptr++;
	}

	return fc_res + 1 - fc_a;
}

/*
 * 根据解析的文本生成tsvector的值
 *
 * 注意：释放prs->words及附属数据。
 */
TSVector
make_tsvector(ParsedText *fc_prs)
{
	int			fc_i,
				fc_j,
				fc_lenstr = 0,
				fc_totallen;
	TSVector	fc_in;
	WordEntry  *fc_ptr;
	char	   *fc_str;
	int			fc_stroff;

	/* 合并重复单词 */
	if (fc_prs->curwords > 0)
		fc_prs->curwords = fc_uniqueWORD(fc_prs->words, fc_prs->curwords);

	/* 确定所需空间 */
	for (fc_i = 0; fc_i < fc_prs->curwords; fc_i++)
	{
		fc_lenstr += fc_prs->words[fc_i].len;
		if (fc_prs->words[fc_i].alen)
		{
			fc_lenstr = SHORTALIGN(fc_lenstr);
			fc_lenstr += sizeof(uint16) + fc_prs->words[fc_i].pos.apos[0] * sizeof(WordEntryPos);
		}
	}

	if (fc_lenstr > MAXSTRPOS)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("string is too long for tsvector (%d bytes, max %d bytes)", fc_lenstr, MAXSTRPOS)));

	fc_totallen = CALCDATASIZE(fc_prs->curwords, fc_lenstr);
	fc_in = (TSVector) palloc0(fc_totallen);
	SET_VARSIZE(fc_in, fc_totallen);
	fc_in->size = fc_prs->curwords;

	fc_ptr = ARRPTR(fc_in);
	fc_str = STRPTR(fc_in);
	fc_stroff = 0;
	for (fc_i = 0; fc_i < fc_prs->curwords; fc_i++)
	{
		fc_ptr->len = fc_prs->words[fc_i].len;
		fc_ptr->pos = fc_stroff;
		memcpy(fc_str + fc_stroff, fc_prs->words[fc_i].word, fc_prs->words[fc_i].len);
		fc_stroff += fc_prs->words[fc_i].len;
		pfree(fc_prs->words[fc_i].word);
		if (fc_prs->words[fc_i].alen)
		{
			int			fc_k = fc_prs->words[fc_i].pos.apos[0];
			WordEntryPos *fc_wptr;

			if (fc_k > 0xFFFF)
				elog(ERROR, "positions array too long");

			fc_ptr->haspos = 1;
			fc_stroff = SHORTALIGN(fc_stroff);
			*(uint16 *) (fc_str + fc_stroff) = (uint16) fc_k;
			fc_wptr = POSDATAPTR(fc_in, fc_ptr);
			for (fc_j = 0; fc_j < fc_k; fc_j++)
			{
				WEP_SETWEIGHT(fc_wptr[fc_j], 0);
				WEP_SETPOS(fc_wptr[fc_j], fc_prs->words[fc_i].pos.apos[fc_j + 1]);
			}
			fc_stroff += sizeof(uint16) + fc_k * sizeof(WordEntryPos);
			pfree(fc_prs->words[fc_i].pos.apos);
		}
		else
			fc_ptr->haspos = 0;
		fc_ptr++;
	}

	if (fc_prs->words)
		pfree(fc_prs->words);

	return fc_in;
}

Datum to_tsvector_byid(PG_FUNCTION_ARGS)
{
	Oid			fc_cfgId = PG_GETARG_OID(0);
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	ParsedText	fc_prs;
	TSVector	fc_out;

	fc_prs.lenwords = VARSIZE_ANY_EXHDR(fc_in) / 6;	/* 只是对单词数量的估计 */
	if (fc_prs.lenwords < 2)
		fc_prs.lenwords = 2;
	else if (fc_prs.lenwords > MaxAllocSize / sizeof(ParsedWord))
		fc_prs.lenwords = MaxAllocSize / sizeof(ParsedWord);
	fc_prs.curwords = 0;
	fc_prs.pos = 0;
	fc_prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * fc_prs.lenwords);

	parsetext(fc_cfgId, &fc_prs, VARDATA_ANY(fc_in), VARSIZE_ANY_EXHDR(fc_in));

	PG_FREE_IF_COPY(fc_in, 1);

	fc_out = make_tsvector(&fc_prs);

	PG_RETURN_TSVECTOR(fc_out);
}

Datum to_tsvector(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(0);
	Oid			fc_cfgId;

	fc_cfgId = getTSCurrentConfig(true);
	PG_RETURN_DATUM(DirectFunctionCall2(to_tsvector_byid,
										ObjectIdGetDatum(fc_cfgId),
										PointerGetDatum(fc_in)));
}

/*
 * jsonb(_string)_to_tsvector(_byid)的工作函数
 */
static TSVector fc_jsonb_to_tsvector_worker(Oid fc_cfgId, Jsonb *fc_jb, uint32 fc_flags)
{
	TSVectorBuildState fc_state;
	ParsedText	fc_prs;

	fc_prs.words = NULL;
	fc_prs.curwords = 0;
	fc_state.prs = &fc_prs;
	fc_state.cfgId = fc_cfgId;

	iterate_jsonb_values(fc_jb, fc_flags, &fc_state, fc_add_to_tsvector);

	return make_tsvector(&fc_prs);
}

Datum jsonb_string_to_tsvector_byid(PG_FUNCTION_ARGS)
{
	Oid			fc_cfgId = PG_GETARG_OID(0);
	Jsonb	   *fc_jb = PG_GETARG_JSONB_P(1);
	TSVector	fc_result;

	fc_result = fc_jsonb_to_tsvector_worker(fc_cfgId, fc_jb, jtiString);
	PG_FREE_IF_COPY(fc_jb, 1);

	PG_RETURN_TSVECTOR(fc_result);
}

Datum jsonb_string_to_tsvector(PG_FUNCTION_ARGS)
{
	Jsonb	   *fc_jb = PG_GETARG_JSONB_P(0);
	Oid			fc_cfgId;
	TSVector	fc_result;

	fc_cfgId = getTSCurrentConfig(true);
	fc_result = fc_jsonb_to_tsvector_worker(fc_cfgId, fc_jb, jtiString);
	PG_FREE_IF_COPY(fc_jb, 0);

	PG_RETURN_TSVECTOR(fc_result);
}

Datum jsonb_to_tsvector_byid(PG_FUNCTION_ARGS)
{
	Oid			fc_cfgId = PG_GETARG_OID(0);
	Jsonb	   *fc_jb = PG_GETARG_JSONB_P(1);
	Jsonb	   *fc_jbFlags = PG_GETARG_JSONB_P(2);
	TSVector	fc_result;
	uint32		fc_flags = parse_jsonb_index_flags(fc_jbFlags);

	fc_result = fc_jsonb_to_tsvector_worker(fc_cfgId, fc_jb, fc_flags);
	PG_FREE_IF_COPY(fc_jb, 1);
	PG_FREE_IF_COPY(fc_jbFlags, 2);

	PG_RETURN_TSVECTOR(fc_result);
}

Datum jsonb_to_tsvector(PG_FUNCTION_ARGS)
{
	Jsonb	   *fc_jb = PG_GETARG_JSONB_P(0);
	Jsonb	   *fc_jbFlags = PG_GETARG_JSONB_P(1);
	Oid			fc_cfgId;
	TSVector	fc_result;
	uint32		fc_flags = parse_jsonb_index_flags(fc_jbFlags);

	fc_cfgId = getTSCurrentConfig(true);
	fc_result = fc_jsonb_to_tsvector_worker(fc_cfgId, fc_jb, fc_flags);
	PG_FREE_IF_COPY(fc_jb, 0);
	PG_FREE_IF_COPY(fc_jbFlags, 1);

	PG_RETURN_TSVECTOR(fc_result);
}

/*
 * json(_string)_to_tsvector(_byid)的工作函数
 */
static TSVector fc_json_to_tsvector_worker(Oid fc_cfgId, text *fc_json, uint32 fc_flags)
{
	TSVectorBuildState fc_state;
	ParsedText	fc_prs;

	fc_prs.words = NULL;
	fc_prs.curwords = 0;
	fc_state.prs = &fc_prs;
	fc_state.cfgId = fc_cfgId;

	iterate_json_values(fc_json, fc_flags, &fc_state, fc_add_to_tsvector);

	return make_tsvector(&fc_prs);
}

Datum json_string_to_tsvector_byid(PG_FUNCTION_ARGS)
{
	Oid			fc_cfgId = PG_GETARG_OID(0);
	text	   *fc_json = PG_GETARG_TEXT_P(1);
	TSVector	fc_result;

	fc_result = fc_json_to_tsvector_worker(fc_cfgId, fc_json, jtiString);
	PG_FREE_IF_COPY(fc_json, 1);

	PG_RETURN_TSVECTOR(fc_result);
}

Datum json_string_to_tsvector(PG_FUNCTION_ARGS)
{
	text	   *fc_json = PG_GETARG_TEXT_P(0);
	Oid			fc_cfgId;
	TSVector	fc_result;

	fc_cfgId = getTSCurrentConfig(true);
	fc_result = fc_json_to_tsvector_worker(fc_cfgId, fc_json, jtiString);
	PG_FREE_IF_COPY(fc_json, 0);

	PG_RETURN_TSVECTOR(fc_result);
}

Datum json_to_tsvector_byid(PG_FUNCTION_ARGS)
{
	Oid			fc_cfgId = PG_GETARG_OID(0);
	text	   *fc_json = PG_GETARG_TEXT_P(1);
	Jsonb	   *fc_jbFlags = PG_GETARG_JSONB_P(2);
	TSVector	fc_result;
	uint32		fc_flags = parse_jsonb_index_flags(fc_jbFlags);

	fc_result = fc_json_to_tsvector_worker(fc_cfgId, fc_json, fc_flags);
	PG_FREE_IF_COPY(fc_json, 1);
	PG_FREE_IF_COPY(fc_jbFlags, 2);

	PG_RETURN_TSVECTOR(fc_result);
}

Datum json_to_tsvector(PG_FUNCTION_ARGS)
{
	text	   *fc_json = PG_GETARG_TEXT_P(0);
	Jsonb	   *fc_jbFlags = PG_GETARG_JSONB_P(1);
	Oid			fc_cfgId;
	TSVector	fc_result;
	uint32		fc_flags = parse_jsonb_index_flags(fc_jbFlags);

	fc_cfgId = getTSCurrentConfig(true);
	fc_result = fc_json_to_tsvector_worker(fc_cfgId, fc_json, fc_flags);
	PG_FREE_IF_COPY(fc_json, 0);
	PG_FREE_IF_COPY(fc_jbFlags, 1);

	PG_RETURN_TSVECTOR(fc_result);
}

/*
 * 解析json(b)值的元素中的词素，添加到TSVectorBuildState。
 */
static void fc_add_to_tsvector(void *fc__state, char *fc_elem_value, int fc_elem_len)
{
	TSVectorBuildState *fc_state = (TSVectorBuildState *) fc__state;
	ParsedText *fc_prs = fc_state->prs;
	int32		fc_prevwords;

	if (fc_prs->words == NULL)
	{
		/*
		 * 第一次通过：将单词数组初始化为合理大小。（parsetext()会根据需要重新分配更大的空间。）
		 */
		fc_prs->lenwords = 16;
		fc_prs->words = (ParsedWord *) palloc(sizeof(ParsedWord) * fc_prs->lenwords);
		fc_prs->curwords = 0;
		fc_prs->pos = 0;
	}

	fc_prevwords = fc_prs->curwords;

	parsetext(fc_state->cfgId, fc_prs, fc_elem_value, fc_elem_len);

	/*
	 * 如果我们从这个JSON元素中提取了任何单词，则前进位置以在元素之间创建一个人工断点。这是因为我们不希望短语搜索认为该元素中的最后一个单词与下一个元素中的第一个单词相邻。
	 */
	if (fc_prs->curwords > fc_prevwords)
		fc_prs->pos += 1;
}


/*
 * to_tsquery
 */


/*
 * 此函数用于形态解析。
 *
 * 该值被传递给parsetext，它将调用正确的字典来词化单词。如果它被确定为停用词，我们将QI_VALSTOP推送到堆栈。
 *
 * 所有属于同一变体的单词都作为AND列表推送，而不同的变体则一起OR。
 */
static void fc_pushval_morph(Datum fc_opaque, TSQueryParserState fc_state, char *fc_strval, int fc_lenval, int16 fc_weight, bool fc_prefix)
{
	int32		fc_count = 0;
	ParsedText	fc_prs;
	uint32		fc_variant,
				fc_pos = 0,
				fc_cntvar = 0,
				fc_cntpos = 0,
				fc_cnt = 0;
	MorphOpaque *fc_data = (MorphOpaque *) DatumGetPointer(fc_opaque);

	fc_prs.lenwords = 4;
	fc_prs.curwords = 0;
	fc_prs.pos = 0;
	fc_prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * fc_prs.lenwords);

	parsetext(fc_data->cfg_id, &fc_prs, fc_strval, fc_lenval);

	if (fc_prs.curwords > 0)
	{
		while (fc_count < fc_prs.curwords)
		{
			/*
			 * 是否移除了任何停用词？如果是，则用合适的运算符填充空位置的占位符。
			 */
			if (fc_pos > 0 && fc_pos + 1 < fc_prs.words[fc_count].pos.pos)
			{
				while (fc_pos + 1 < fc_prs.words[fc_count].pos.pos)
				{
					/* 为每个缺失的停用词放置占位符 */
					pushStop(fc_state);
					if (fc_cntpos)
						pushOperator(fc_state, fc_data->qoperator, 1);
					fc_cntpos++;
					fc_pos++;
				}
			}

			/* 保存当前单词的位置 */
			fc_pos = fc_prs.words[fc_count].pos.pos;

			/* 遍历从该标记获得的所有变体 */
			fc_cntvar = 0;
			while (fc_count < fc_prs.curwords && fc_pos == fc_prs.words[fc_count].pos.pos)
			{
				fc_variant = fc_prs.words[fc_count].nvariant;

				/* 推送所有属于同一变体的单词 */
				fc_cnt = 0;
				while (fc_count < fc_prs.curwords &&
					   fc_pos == fc_prs.words[fc_count].pos.pos &&
					   fc_variant == fc_prs.words[fc_count].nvariant)
				{
					pushValue(fc_state,
							  fc_prs.words[fc_count].word,
							  fc_prs.words[fc_count].len,
							  fc_weight,
							  ((fc_prs.words[fc_count].flags & TSL_PREFIX) || fc_prefix));
					pfree(fc_prs.words[fc_count].word);
					if (fc_cnt)
						pushOperator(fc_state, OP_AND, 0);
					fc_cnt++;
					fc_count++;
				}

				if (fc_cntvar)
					pushOperator(fc_state, OP_OR, 0);
				fc_cntvar++;
			}

			if (fc_cntpos)
			{
				/* 距离可能有用 */
				pushOperator(fc_state, fc_data->qoperator, 1);
			}

			fc_cntpos++;
		}

		pfree(fc_prs.words);
	}
	else
		pushStop(fc_state);
}

Datum to_tsquery_byid(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	TSQuery		fc_query;
	MorphOpaque fc_data;

	fc_data.cfg_id = PG_GETARG_OID(0);

	/*
	 * 传递OP_PHRASE作为qoperator使得tsquery要求复杂形态的单词位置完全匹配tsvector。此外，当复杂形态通过OP_PHRASE运算符连接时，我们将它们的所有单词连接成OP_PHRASE序列。
	 */
	fc_data.qoperator = OP_PHRASE;

	fc_query = parse_tsquery(text_to_cstring(fc_in),
						  fc_pushval_morph,
						  PointerGetDatum(&fc_data),
						  0);

	PG_RETURN_TSQUERY(fc_query);
}

Datum to_tsquery(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(0);
	Oid			fc_cfgId;

	fc_cfgId = getTSCurrentConfig(true);
	PG_RETURN_DATUM(DirectFunctionCall2(to_tsquery_byid,
										ObjectIdGetDatum(fc_cfgId),
										PointerGetDatum(fc_in)));
}

Datum plainto_tsquery_byid(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	TSQuery		fc_query;
	MorphOpaque fc_data;

	fc_data.cfg_id = PG_GETARG_OID(0);

	/*
	 * 带有P_TSQ_PLAIN标志的parse_tsquery()将整个输入文本视为单个形态。传递OP_PHRASE作为qoperator使得tsquery要求独立于单词位置匹配所有单词。
	 */
	fc_data.qoperator = OP_AND;

	fc_query = parse_tsquery(text_to_cstring(fc_in),
						  fc_pushval_morph,
						  PointerGetDatum(&fc_data),
						  P_TSQ_PLAIN);

	PG_RETURN_POINTER(fc_query);
}

Datum plainto_tsquery(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(0);
	Oid			fc_cfgId;

	fc_cfgId = getTSCurrentConfig(true);
	PG_RETURN_DATUM(DirectFunctionCall2(plainto_tsquery_byid,
										ObjectIdGetDatum(fc_cfgId),
										PointerGetDatum(fc_in)));
}


Datum phraseto_tsquery_byid(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	TSQuery		fc_query;
	MorphOpaque fc_data;

	fc_data.cfg_id = PG_GETARG_OID(0);

	/*
	 * parse_tsquery()带有P_TSQ_PLAIN标志将整个输入文本作为一个
	 * 单一形态处理。将OP_PHRASE作为qoperator会使tsquery要求
	 * 匹配单词位置。
	 */
	fc_data.qoperator = OP_PHRASE;

	fc_query = parse_tsquery(text_to_cstring(fc_in),
						  fc_pushval_morph,
						  PointerGetDatum(&fc_data),
						  P_TSQ_PLAIN);

	PG_RETURN_TSQUERY(fc_query);
}

Datum phraseto_tsquery(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(0);
	Oid			fc_cfgId;

	fc_cfgId = getTSCurrentConfig(true);
	PG_RETURN_DATUM(DirectFunctionCall2(phraseto_tsquery_byid,
										ObjectIdGetDatum(fc_cfgId),
										PointerGetDatum(fc_in)));
}

Datum websearch_to_tsquery_byid(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	MorphOpaque fc_data;
	TSQuery		fc_query = NULL;

	fc_data.cfg_id = PG_GETARG_OID(0);

	/*
	 * 将OP_PHRASE作为qoperator会使tsquery要求复杂形态的单词
	 * 位置与tsvector完全匹配。另外，当复杂形态用引号给出时，
	 * 我们将它们所有的单词连接成OP_PHRASE序列。
	 */
	fc_data.qoperator = OP_PHRASE;

	fc_query = parse_tsquery(text_to_cstring(fc_in),
						  fc_pushval_morph,
						  PointerGetDatum(&fc_data),
						  P_TSQ_WEB);

	PG_RETURN_TSQUERY(fc_query);
}

Datum websearch_to_tsquery(PG_FUNCTION_ARGS)
{
	text	   *fc_in = PG_GETARG_TEXT_PP(0);
	Oid			fc_cfgId;

	fc_cfgId = getTSCurrentConfig(true);
	PG_RETURN_DATUM(DirectFunctionCall2(websearch_to_tsquery_byid,
										ObjectIdGetDatum(fc_cfgId),
										PointerGetDatum(fc_in)));
}
